﻿using Letter.IO.Scheduler;
using ePipe;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Letter.IO.Memorizer;
using Letter.Net.Session;

namespace Letter.Net.Tcp;

sealed class TcpClient : ATcpHost<TcpClientTransportOptions, TcpClientFeatureOptions>, ITcpClient
{
    public TcpClient(string name) : base(name)
    {
    }

    private IChannelSettings setting;
    private EndPoint bindAddress;

    private ISession transport;

    public override EndPoint BindEndPoint
    {
        get { return this.bindAddress; }
    }
    
    public override void Build()
    {
        base.Build();

        var emptyMaxReadBufferSize = (this.transportOptions.MaxReadBufferSize == null);
        var emptyMaxWriteBufferSize = (this.transportOptions.MaxWriteBufferSize == null);
        var maxReadBufferSize = emptyMaxReadBufferSize ? 0 : this.transportOptions.MaxReadBufferSize.Value;
        var maxWriteBufferSize = emptyMaxWriteBufferSize ? 0 : this.transportOptions.MaxWriteBufferSize.Value;
        
        var applicationScheduler = PipeScheduler.ThreadPool;
        var transportScheduler = SchedulerUtil.SelectSocketsScheduler();
        this.setting = ChannelSettingsProvider.CreateSettings
        (
            transportScheduler,
            new PipeOptions<byte>(
                                            MemoryPoolProvider.ShareMemoryPool<byte>(),
                                            applicationScheduler,
                                            transportScheduler,
                                            maxReadBufferSize,
                                            maxReadBufferSize / 2,
                                            useSynchronizationContext: false),
            new PipeOptions<byte>(
                                            MemoryPoolProvider.ShareMemoryPool<byte>(),
                                            transportScheduler,
                                            applicationScheduler,
                                            maxWriteBufferSize,
                                            maxWriteBufferSize / 2,
                                            useSynchronizationContext: false),
            MemoryPoolProvider.ShareMemoryPool<byte>()
        );
    }

    public override async Task ConnectAsync(EndPoint endPoint)
    {
        var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        await socket.ConnectAsync(endPoint);
        this.bindAddress = socket.LocalEndPoint;

        await this.StartSocketTransport(socket);
    }

    private async ValueTask StartSocketTransport(Socket socket)
    {
        TcpSocketFeatureSetting.SettingFeature(socket, this.featureOptions);
        var connection = this.CreateConnection();
        var transmitter = this.transportFactory(socket, setting, connection);
        await transmitter.StartAsync();
        
        this.transport = transmitter;
    }

    public override async Task StopAsync()
    {
        await this.transport.CloseAsync();
    }
}